/* Copyright (c) 2009 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jessies.blogbaboon;

import e.util.*;
import org.jessies.test.*;

/**
 * Translates in both directions between our own wiki-like markup and HTML.
 * 
 * Here's a brief summary of supported syntax:
 * 
 * *bold*
 * 
 * _italic_
 * 
 * {{{
 *   if (x > y) {
 *     f(x, y);
 *   }
 * }}}
 * 
 *  Blockquoted material is indented by at
 *  least one space.
 * 
 * [http://code.google.com/p/blog-baboon/ BlogBaboon]
 * 
 * What's not supported?
 * 
 * We don't support lists (numbered or unnumbered), though they would be useful.
 * 
 * We don't support = heading = and == subheading == styles (and so forth), because
 * their usefulness is limited in blog posts by the fact that blogger.com uses h1 to h3
 * for the text surrounding the post, and h4, h5, and h6 are -- by default at least --
 * a bit too small to really stand out. But people could fix their CSS, so there's no
 * fundamental reason for us not to add this.
 * 
 * Tables would be cool, but they're not supported yet.
 * 
 * Embedded HTML isn't supported, and has always struck me as a bad idea.
 * 
 * Here's the full documentation for the code.google.com wiki syntax:
 * http://code.google.com/p/support/wiki/WikiSyntax
 */
public class WikiCodec {
  public String toHtml(String content) {
    final StringBuilder result = new StringBuilder(content.length());
    
    boolean newParagraphNext = true;
    boolean inBlockquote = false;
    boolean inPre = false;
    for (String line : StringUtilities.escapeForHtml(content).split("\n")) {
      // Deal with entering and exiting preformatted blocks.
      if (line.equals("{{{")) {
        result.append("<pre>");
        inPre = true;
        continue;
      } else if (line.equals("}}}")) {
        result.append("</pre>\n");
        inPre = false;
        newParagraphNext = true;
        continue;
      }
      
      // Handle preformatted lines if we're in a preformatted block.
      if (inPre) {
        // Rewrite leading spaces as "&nbsp;" so the browser pays attention.
        int i = 0;
        for (; i < line.length() && isSpace(line.charAt(i)); ++i) {
          result.append("&nbsp;");
        }
        // Output the rest of the line.
        result.append(line.substring(i));
        // But we do want a newline. Unlike spaces, they count here.
        result.append("\n");
        continue;
      }
      
      // A blank line doesn't produce any output itself, but it implies a new paragraph comes next.
      if (isAllSpace(line)) {
        newParagraphNext = true;
        continue;
      }
      
      // Handle blockquoting.
      if (line.charAt(0) == ' ') {
        if (!inBlockquote) {
          result.append("<blockquote>");
          result.append(formatTextChunk(line));
          inBlockquote = true;
          continue;
        }
      } else if (inBlockquote) {
        result.append("</blockquote>");
        inBlockquote = false;
        // Fall through; the line that marked the end of the blockquote is normal content...
      }
      
      if (newParagraphNext) {
        result.append("<p>");
        newParagraphNext = false;
      }
      result.append(formatTextChunk(line));
      result.append("\n");
    }
    
    return result.toString();
  }
  
  private static boolean isAllSpace(String s) {
    for (int i = 0; i < s.length(); ++i) {
      if (!isSpace(s.charAt(i))) {
        return false;
      }
    }
    return true;
  }
  
  private static boolean isSpace(char ch) {
    return ch == ' ' || ch == '\t';
  }
  
  private String formatTextChunk(String s) {
    return rewriteLinks(applyStyles(applyDashes(s)));
  }
  
  private String applyDashes(String s) {
    // -- is an n-dash.
    s = s.replace(" -- ", "&nbsp;&ndash; ");
    // --- is an m-dash.
    s = s.replace(" --- ", "&nbsp;&mdash; ");
    return s;
  }
  
  private String applyStyles(String s) {
    // {{{inline code}}}
    // FIXME: this should be done elsewhere, to ensure that $1 doesn't have any other transformations applied to it.
    // Something like {{{_main_}}} shouldn't be italic, for example; those ought to be literal underscores.
    s = s.replaceAll("\\{\\{\\{(.+?)\\}\\}\\}", "<tt>$1</tt>");
    // _italic text_
    s = s.replaceAll("\\b_(.+?)_\\b", "<i>$1</i>");
    // *bold text*
    // FIXME: \b is insufficient here because * itself counts (unlike _ in the previous RE).
    s = s.replaceAll("\\*(.+?)\\*", "<b>$1</b>");
    return s;
  }
  
  @Test private static void testToHtml() {
    Assert.contains(new WikiCodec().toHtml("SOME_MACRO_NAME"), "SOME_MACRO_NAME");
    Assert.contains(new WikiCodec().toHtml("some _MACRO_ name"), "some <i>MACRO</i> name");
    
    // BROKEN Assert.contains(new WikiCodec().toHtml("x*y*z"), "x*y*z");
    // BROKEN Assert.contains(new WikiCodec().toHtml("some *bold* text"), "some <b>bold</b> text");
  }
  
  private String rewriteLinks(String s) {
    // FIXME: this should be done elsewhere, to ensure that $1 doesn't have any other transformations applied to it.
    // Note however that $2 *should* have the other transformations applied.
    // [http://code.google.com/p/blog-baboon BlogBaboon]
    return s.replaceAll("\\[(http.+?) (.+?)\\]", "<a href=\"$1\">$2</a>");
  }
  
  public String fromHtml(String content) {
    // We never output this markup ourselves, but it helps us be more compatible with legacy posts.
    content = content.replaceAll("<br ?/?>", "\n");
    content = content.replace("<code>", "<tt>");
    content = content.replace("</code>", "</tt>");
    
    content = content.replaceAll("<p>", "\n");
    
    content = content.replace("&nbsp;&ndash; ", " -- ");
    content = content.replace("&nbsp;&mdash; ", " --- ");
    
    content = content.replaceAll("<tt>(.+?)</tt>", "{{{$1}}}");
    content = content.replaceAll("<i>(.+?)</i>", "_$1_");
    content = content.replaceAll("<b>(.+?)</b>", "*$1*");
    
    content = content.replaceAll("<pre>", "{{{\n");
    content = content.replaceAll("</pre>", "}}}");
    content = content.replace("&nbsp;", " ");
    
    content = content.replaceAll("<a href=\"(.+?)\">(.+?)</a>", "[$1 $2]");
    
    content = new Rewriter("<blockquote>(?s)(.*?)</blockquote>") {
      public String replacement() {
        return " " + StringUtilities.join(group(1).split("\n"), "\n ");
      }
    }.rewrite(content);
    
    content = StringUtilities.unescapeHtml(content);
    
    return content;
  }
}
